Optimisation système d'une boucle d'apprentissage Resnet-50.

Le but de ce notebook est d'optimiser un code d'apprentissage d'un modèle Resnet-50 sur Imagenet pour Jean Zay en implémentant :
Les cellules dans ce notebook ne sont pas prévues pour être modifiées, sauf rares exceptions indiquées dans les commentaires. Les TP se feront en modifiant le code dlojz.py.
Les directives de modification seront marquées par l'étiquette TODO :, dans le notebook suivant.
Les solutions sont présentes dans le répertoire solutions.
Notebook rédigé par l'équipe assistance IA de l'IDRIS, février 2023
Ce notebook est prévu pour être exécuté à partir d'une machine frontale de Jean-Zay. Le hostname doit être jean-zay[1-5].
!hostname
jean-zay-srv2
Un module PyTorch doit avoir été chargé pour le bon fonctionnement de ce Notebook. Nécessairement, le module pytorch-gpu/py3/1.11.0 :
!module list
Currently Loaded Modulefiles: 1) cuda/11.2 5) openmpi/4.1.1-cuda 9) sparsehash/2.0.3 2) nccl/2.9.6-1-cuda 6) intel-mkl/2020.4 10) libjpeg-turbo/2.1.3 3) cudnn/8.1.1.33-cuda 7) magma/2.5.4-cuda 11) pytorch-gpu/py3/1.11.0 4) gcc/8.4.1(8.3.1) 8) sox/14.4.2 >
Les fonctions python de gestion de queue SLURM dévelopées par l'IDRIS et les fonctions dédiées à la formation DLO-JZ sont à importer.
Le module d'environnement pour les jobs et la taille des images sont fixés pour ce notebook.
TODO : choisir un pseudonyme (maximum 5 caractères) pour vous différencier dans la queue SLURM et dans les outils collaboratifs pendant la formation et la compétition.
from idr_pytools import display_slurm_queue, gpu_jobs_submitter, search_log
from dlojz_tools import controle_technique, compare, GPU_underthehood, plot_accuracy, lrfind_plot, imagenet_starter
MODULE = 'pytorch-gpu/py3/1.11.0'
image_size = 176
account = 'for@v100'
name = 'pseudo' ## Pseudonyme à choisir
Creation d'un repertoire checkpoints si cela n'a pas déjà été fait.
!mkdir checkpoints
mkdir: cannot create directory ‘checkpoints’: File exists
Cette partie permet d'afficher et de gérer la queue SLURM.
Pour afficher toute la queue utilisateur :
display_slurm_queue()
Done!
Remarque: Cette fonction utilisée plusieurs fois dans ce notebook permet d'afficher la queue de manière dynamique, rafraichie toutes les 5 secondes. Cependant elle ne s'arrête que lorsque la queue est vide. Si vous désirez reprendre la main sur le notebook, il vous suffira d'arrêter manuellement la cellule avec le bouton stop. Cela a bien sûr aucun impact sur le scheduler SLURM. Les jobs ne seront pas arrêtés.
Si vous voulez arrêter des jobs dans la queue:
!scancel -u $USER#!scancel -u $USER
Cette partie debug permet d'afficher les fichiers de sortie et les fichiers d'erreur du job.
Il est nécessaire dans la cellule suivante (en décommentant) d'indiquer le jobid correspondant sous le format suivant.
*Remarque* : dans ce notebook, lorsque vous soumettrez un job, vous recevrez en retour le numéro du job dans le format suivant : jobid = ['123456']. La cellule ci-dessous peut ainsi être facilement actualisée."
jobid = ['1493206']
Fichier de sortie :
%cat {search_log(contains=jobid[0])[0]}
>>> Training on 1 nodes and 1 processes
model: Resnet-50
number of parameters: 25557032
global batch size: 128 - mini batch size: 128
Optimizer: SGD (
Parameter Group 0
dampening: 0
lr: 0.1
maximize: False
momentum: 0.9
nesterov: False
weight_decay: 0.0
)
DATALOADER 10 True True True 3 False
image batch shape : torch.Size([128, 3, 176, 176])
>>> Training complete in: 0:05:53.561484
>>> Training performance time: min 4.501930236816406 avg 4.766597509384155 seconds (+/- 0.14579007426206964)
>>> Loading performance time: min 0.00013065338134765625 avg 0.00015664100646972656 seconds (+/- 1.434471526857817e-05)
>>> Forward performance time: 1.8760071092722368 seconds (+/- 0.09052225345171229)
>>> Backward performance time: 2.901484557560512 seconds (+/- 0.08457200485922346)
>>> Peak Power during training: 62.22 W)
>>> Validation time estimation: 0:17:49.429900
>>> Sortie trace #####################################
>>>JSON {"GPU process - Forward/Backward": [6.2423787117004395, 5.0220746994018555, 4.957103490829468, 5.0125415325164795, 4.943894863128662, 4.966577529907227, 5.019651651382446, 4.99901819229126, 4.964149236679077, 4.885366678237915, 4.904692649841309, 4.781780242919922, 4.951511859893799, 4.84772253036499, 4.766597509384155, 4.684057712554932, 4.787169456481934, 4.795409679412842, 4.917107105255127, 4.681482791900635, 4.62136173248291, 4.676070213317871, 4.717148303985596, 4.702852964401245, 4.64032506942749, 4.7434868812561035, 4.692547082901001, 4.687806129455566, 4.651872873306274, 4.681207180023193, 4.635786294937134, 4.501930236816406, 4.82633376121521, 4.589520454406738, 4.554682970046997, 4.706907749176025, 4.620109558105469, 4.52482008934021, 4.774549961090088, 4.610376834869385, 4.6381542682647705, 4.710811138153076, 5.109354257583618, 4.724546909332275, 4.806366920471191, 4.937008380889893, 4.881795883178711, 4.729339838027954, 4.814107179641724, 4.803519010543823], "CPU process - Dataloader": [3.979201555252075, 0.0001895427703857422, 0.00014591217041015625, 0.00017380714416503906, 0.00013375282287597656, 0.00013637542724609375, 0.0001621246337890625, 0.0001347064971923828, 0.00015163421630859375, 0.00015854835510253906, 0.00017547607421875, 0.0001685619354248047, 0.00014591217041015625, 0.0001819133758544922, 0.00013637542724609375, 0.0001373291015625, 0.0001327991485595703, 0.00013065338134765625, 0.0001361370086669922, 0.00015592575073242188, 0.0001666545867919922, 0.0001628398895263672, 0.00017642974853515625, 0.00017499923706054688, 0.0001685619354248047, 0.00014543533325195312, 0.00013685226440429688, 0.00013947486877441406, 0.00013518333435058594, 0.00015878677368164062, 0.00015592575073242188, 0.0001609325408935547, 0.0001652240753173828, 0.0001544952392578125, 0.0001742839813232422, 0.00015854835510253906, 0.00015926361083984375, 0.00015854835510253906, 0.0001609325408935547, 0.00016069412231445312, 0.00016307830810546875, 0.00015592575073242188, 0.00015592575073242188, 0.00015783309936523438, 0.00017976760864257812, 0.00015854835510253906, 0.00015735626220703125, 0.00016188621520996094, 0.0001595020294189453, 0.000164031982421875]}
>>> Number of batch per epoch: 10010
Max Memory Allocated 0 Bytes
Tue Feb 21 21:41:18 CET 2023
Fichier d'erreur :
%cat {search_log(contains=jobid[0], with_err=True)['stderr'][0]}
Loading pytorch-gpu/py3/1.11.0
Loading requirement: cuda/11.2 nccl/2.9.6-1-cuda cudnn/8.1.1.33-cuda gcc/8.4.1
openmpi/4.1.1-cuda intel-mkl/2020.4 magma/2.5.4-cuda sox/14.4.2
sparsehash/2.0.3 libjpeg-turbo/2.1.3
+ srun python -u dlojz.py -b 128 --image-size 176 --test
/gpfslocalsup/pub/anaconda-py3/2021.05/envs/pytorch-gpu-1.11.0+py3.9.12/lib/python3.9/site-packages/apex/pyprof/__init__.py:5: FutureWarning: pyprof will be removed by the end of June, 2022
warnings.warn("pyprof will be removed by the end of June, 2022", FutureWarning)
real 6m27.615s
user 0m0.020s
sys 0m0.013s
+ date
Pour le debug ou pour comparer son code avec les solutions mises à disposition, la fonction suivante permet d'afficher une page html contenant un différentiel de fichiers texte.
s1 = "dlojz.py"
s2 = "./solutions/dlojz1_0.py"
compare(s1, s2)
Voir le résultat du différentiel de fichiers sur la page suivante (attention au spoil !) :
import os
import torchvision
import torchvision.transforms as transforms
import torch
import numpy as np
import matplotlib.pyplot as plt
transform = transforms.Compose([
transforms.RandomResizedCrop(176), # Random resize - Data Augmentation
transforms.RandomHorizontalFlip(), # Horizontal Flip - Data Augmentation
transforms.ToTensor(), # convert the PIL Image to a tensor
transforms.Normalize(mean=(0.485, 0.456, 0.406),
std=(0.229, 0.224, 0.225))
])
train_dataset = torchvision.datasets.ImageNet(root=os.environ['ALL_CCFRSCRATCH']+'/imagenet',
transform=transform)
train_dataset
Dataset ImageNet
Number of datapoints: 1281167
Root location: /gpfsscratch/idris/sos/commun/imagenet
Split: train
StandardTransform
Transform: Compose(
RandomResizedCrop(size=(176, 176), scale=(0.08, 1.0), ratio=(0.75, 1.3333), interpolation=bilinear)
RandomHorizontalFlip(p=0.5)
ToTensor()
Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225))
)
train_loader = torch.utils.data.DataLoader(dataset=train_dataset,
batch_size=4,
shuffle=True)
batch = next(iter(train_loader))
print('X train batch, shape: {}, data type: {}, Memory usage: {} bytes'
.format(batch[0].shape, batch[0].dtype, batch[0].element_size()*batch[0].nelement()))
print('Y train batch, shape: {}, data type: {}, Memory usage: {} bytes'
.format(batch[1].shape, batch[1].dtype, batch[1].element_size()*batch[1].nelement()))
img = batch[0][0].numpy().transpose((1,2,0))
plt.imshow(img)
plt.axis('off')
labels_cls, labels_id = torch.load(os.environ['ALL_CCFRSCRATCH']+'/imagenet/meta.bin')
label = labels_cls[labels_id[batch[1][0].numpy()]]
_ = plt.title('label class: {}'.format(label[0]))
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
X train batch, shape: torch.Size([4, 3, 176, 176]), data type: torch.float32, Memory usage: 1486848 bytes Y train batch, shape: torch.Size([4]), data type: torch.int64, Memory usage: 32 bytes
val_transform = transforms.Compose([
transforms.Resize((256, 256)),
transforms.CenterCrop(224),
transforms.ToTensor(), # convert the PIL Image to a tensor
transforms.Normalize(mean=(0.485, 0.456, 0.406),
std=(0.229, 0.224, 0.225))])
val_dataset = torchvision.datasets.ImageNet(root=os.environ['ALL_CCFRSCRATCH']+'/imagenet', split='val',
transform=val_transform)
val_dataset
Dataset ImageNet
Number of datapoints: 50000
Root location: /gpfsscratch/idris/sos/commun/imagenet
Split: val
StandardTransform
Transform: Compose(
Resize(size=(256, 256), interpolation=bilinear, max_size=None, antialias=None)
CenterCrop(size=(224, 224))
ToTensor()
Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225))
)
import torchvision.models as models
model = models.resnet50()
print('number of total parameters: {}'.format(sum([p.numel() for p in model.parameters()])))
print('number of trainable parameters: {}'.format(sum([p.numel() for p in model.parameters() if p.requires_grad])))
number of total parameters: 25557032 number of trainable parameters: 25557032
Ce TP consiste à appliquer le code baseline pour prendre en main les fonctionnalités de test et découvrir le code.
TODO :
dlojz.pyRemarque :
DON'T MODIFY dans le script ne doivent pas être modifiées.n_gpu = 1
batch_size = 128
command = f'dlojz.py -b {batch_size} --image-size {image_size} --test'
command
'dlojz.py -b 128 --image-size 176 --test'
Soumission du job. Attention vous sollicitez les noeuds de calcul à ce moment-là.
Pour soumettre le job, veuillez basculer la cellule suivante du mode Raw NBConvert au mode Code.
Copier-coller la sortie jobid = ['xxxxx'] dans la cellule suivante.
Puis, rebasculer la cellule précédente en mode Raw NBConvert, afin d'eviter de relancer un job par erreur.
#jobid = ['1493206']
display_slurm_queue(name)
Done!
controle_technique(jobid)
Train throughput: 26.85 images/second GPU throughput: 26.85 images/second epoch time: 47715.21 seconds training time estimation for 90 epochs (with validations): 1219.62 hours ----------- training step time average (fwd/bkwd on GPU): 4.766598 sec (39.4%/60.9%) +/- 0.145790 loading step time average (CPU to GPU): 0.000157 sec +/- 0.000014 ----------- ELIGIBLE to run 1 epochs
Le code baseline dlo-jz.py a été exécuté sur le CPU (contrairement à ce qui est indiqué par le contrôle technique) en mode test, soit sur 50 itérations.
Dans le prochain exercice nous verrons ensemble l'accélération sur 1 GPU.

TODO : dans le script dlojz.py :
gpu et envoyer le modèle dans la mémoire du GPU.# define model
gpu = torch.device("cuda")
model = models.resnet50()
model = model.to(gpu)
non_blocking=args.non_blocking sera utile plus tard lors de la définition d'un DataLoader distribué.# distribution of images and labels to all GPUs
images = images.to(gpu, non_blocking=args.non_blocking)
labels = labels.to(gpu, non_blocking=args.non_blocking)
et
# distribution of images and labels to all GPUs
val_images = val_images.to(gpu, non_blocking=args.non_blocking)
val_labels = val_labels.to(gpu, non_blocking=args.non_blocking)
## Initialisation
if idr_torch.rank == 0: accuracies = []
val_loss = torch.Tensor([0.]).to(gpu) # send to GPU
val_accuracy = torch.Tensor([0.]).to(gpu) # send to GPU
n_gpu = 1
batch_size = 128
command = f'dlojz.py -b {batch_size} --image-size {image_size} --test'
command
'dlojz.py -b 128 --image-size 176 --test'
Soumission du job. Attention vous sollicitez les noeuds de calcul à ce moment-là.
Pour soumettre le job, veuillez basculer la cellule suivante du mode Raw NBConvert au mode Code.
Copier-coller la sortie jobid = ['xxxxx'] dans la cellule suivante.
Puis, rebasculer la cellule précédente en mode Raw NBConvert, afin d'eviter de relancer un job par erreur.
#jobid = ['1493317']
display_slurm_queue(name)
Done!
controle_technique(jobid)
Train throughput: 489.71 images/second GPU throughput: 490.38 images/second epoch time: 2616.39 seconds training time estimation for 90 epochs (with validations): 67.88 hours ----------- training step time average (fwd/bkwd on GPU): 0.261022 sec (32.9%/66.9%) +/- 0.002254 loading step time average (CPU to GPU): 0.000356 sec +/- 0.000031 ----------- ELIGIBLE to run 18 epochs
Afin de mesurer l'impact de la taille de batch sur l'occupation mémoire et sur le throughput, la cellule suivante permet de soumettre plusieurs jobs avec des tailles de batch croissantes. Dans les cas où la mémoire est saturée et dépasse la capacité du GPU, le système renverra une erreur CUDA Out of Memory.
Soumission du job. Attention vous sollicitez les noeuds de calcul à ce moment-là.
Pour soumettre le job, veuillez basculer la cellule suivante du mode Raw NBConvert au mode Code.
Copier-coller la sortie jobids = ['xxxxx', ...] dans la cellule suivante.
Puis, rebasculer la cellule précédente en mode Raw NBConvert, afin d'eviter de relancer un job par erreur.
#jobids = ['1493424', '1493432', '1493433', '1493434', '1493435', '1493436', '1493438', '1493440']
display_slurm_queue(name)
Done!
GPU_underthehood(jobids)
Batch size per GPU: 8 Max GPU Memory Allocated: 2.93 GB, Troughput: 237.970 images/second Batch size per GPU: 16 Max GPU Memory Allocated: 2.93 GB, Troughput: 346.746 images/second Batch size per GPU: 32 Max GPU Memory Allocated: 2.94 GB, Troughput: 399.781 images/second Batch size per GPU: 64 Max GPU Memory Allocated: 3.55 GB, Troughput: 440.075 images/second Batch size per GPU: 128 Max GPU Memory Allocated: 6.76 GB, Troughput: 490.162 images/second Batch size per GPU: 256 Max GPU Memory Allocated: 13.20 GB, Troughput: 482.640 images/second Batch size per GPU: 512 Max GPU Memory Allocated: 26.08 GB, Troughput: 483.815 images/second Batch size per GPU: 1024 CUDA out of memory Memory occupancy by Model part : 0.331 +/- 0.011 GB
Le dernier job a atteint le seuil CUDA Out Of Memory :
controle_technique([jobids[-1]])

TODO : dans le script dlojz.py :
from torch.cuda.amp import autocast, GradScaler
# Creates a GradScaler once at the beginning of training.
scaler = GradScaler()
with autocast(): dans la boucle de training et la boucle de validation....
# Runs the forward pass with autocasting.
with autocast():
outputs = model(images)
loss = criterion(outputs, labels)
...
loss.backward() et optimizer.step().# Scales loss. Calls backward() on scaled loss to create scaled gradients.
# Backward passes under autocast are not recommended.
# Backward ops run in the same dtype autocast chose for corresponding forward ops.
scaler.scale(loss).backward()
# scaler.step() first unscales the gradients of the optimizer's assigned params.
# If these gradients do not contain infs or NaNs, optimizer.step() is then called,
# otherwise, optimizer.step() is skipped.
scaler.step(optimizer)
# Updates the scale for next iteration.
scaler.update()
n_gpu = 1
batch_size = 128
command = f'dlojz.py -b {batch_size} --image-size {image_size} --test'
command
'dlojz.py -b 128 --image-size 176 --test'
Soumission du job. Attention vous sollicitez les noeuds de calcul à ce moment-là.
Pour soumettre le job, veuillez basculer la cellule suivante du mode Raw NBConvert au mode Code.
Copier-coller la sortie jobid = ['xxxxx'] dans la cellule suivante.
Puis, rebasculer la cellule précédente en mode Raw NBConvert, afin d'eviter de relancer un job par erreur.
#jobid = ['1493501']
display_slurm_queue(name)
Done!
controle_technique(jobid)
Train throughput: 985.94 images/second GPU throughput: 988.15 images/second epoch time: 1299.55 seconds training time estimation for 90 epochs (with validations): 34.32 hours ----------- training step time average (fwd/bkwd on GPU): 0.129535 sec (37.0%/63.2%) +/- 0.003082 loading step time average (CPU to GPU): 0.000290 sec +/- 0.000028 ----------- ELIGIBLE to run 30 epochs
Afin de mesurer l'impact de la taille de batch sur l'occupation mémoire et sur le throughput, la cellule suivante permet de soumettre plusieurs jobs avec des tailles de batch croissantes. Dans les cas où la mémoire est saturée et dépasse la capacité du GPU, le système renverra une erreur CUDA Out of Memory.
Soumission du job. Attention vous sollicitez les noeuds de calcul à ce moment-là.
Pour soumettre le job, veuillez basculer la cellule suivante du mode Raw NBConvert au mode Code.
Copier-coller la sortie jobids = ['xxxxx', ...] dans la cellule suivante.
Puis, rebasculer la cellule précédente en mode Raw NBConvert, afin d'eviter de relancer un job par erreur.
#jobids = ['1493508', '1493509', '1493511', '1493512', '1493513', '1493515', '1493516', '1493517']
display_slurm_queue(name)
Done!
GPU_underthehood(jobids)
Batch size per GPU: 16 Max GPU Memory Allocated: 1.78 GB, Troughput: 369.803 images/second Batch size per GPU: 32 Max GPU Memory Allocated: 1.79 GB, Troughput: 638.925 images/second Batch size per GPU: 64 Max GPU Memory Allocated: 2.01 GB, Troughput: 908.608 images/second Batch size per GPU: 128 Max GPU Memory Allocated: 3.68 GB, Troughput: 990.135 images/second Batch size per GPU: 256 Max GPU Memory Allocated: 7.00 GB, Troughput: 1031.320 images/second Batch size per GPU: 512 Max GPU Memory Allocated: 13.66 GB, Troughput: 1067.331 images/second Batch size per GPU: 1024 Max GPU Memory Allocated: 26.95 GB, Troughput: 1004.053 images/second Batch size per GPU: 2048 CUDA out of memory Memory occupancy by Model part : 0.353 +/- 0.014 GB
TODO : Choisir pour la suite du TP une taille de batch par GPU qui vous semble la plus pertinente selon le test précédent.
## Choisir un batch size optimal
bs_optim = 512
n_gpu = 1
command = f'dlojz.py -b {bs_optim} --image-size {image_size} --test'
command
'dlojz.py -b 512 --image-size 176 --test'
Soumission du job. Attention vous sollicitez les noeuds de calcul à ce moment-là.
Pour soumettre le job, veuillez basculer la cellule suivante du mode Raw NBConvert au mode Code.
Copier-coller la sortie jobids = ['xxxxx', ...] dans la cellule suivante.
Puis, rebasculer la cellule précédente en mode Raw NBConvert, afin d'eviter de relancer un job par erreur.
#jobid = ['1493563']
display_slurm_queue(name)
Done!
controle_technique(jobid)
Train throughput: 1056.33 images/second GPU throughput: 1063.13 images/second epoch time: 1213.19 seconds training time estimation for 90 epochs (with validations): 32.49 hours ----------- training step time average (fwd/bkwd on GPU): 0.481595 sec (40.3%/67.7%) +/- 0.094326 loading step time average (CPU to GPU): 0.003101 sec +/- 0.017646 ----------- ELIGIBLE to run 31 epochs

TODO : dans le script dlojz.py :
memory_format avec torch.channels_last.# define model
gpu = torch.device("cuda")
model = models.resnet50()
model = model.to(gpu, memory_format=torch.channels_last)
memory_format avec torch.channels_last.# distribution of images and labels to all GPUs
images = images.to(gpu, non_blocking=args.non_blocking, memory_format=torch.channels_last)
labels = labels.to(gpu, non_blocking=args.non_blocking)
et
# distribution of images and labels to all GPUs
val_images = val_images.to(gpu, non_blocking=args.non_blocking, memory_format=torch.channels_last)
val_labels = val_labels.to(gpu, non_blocking=args.non_blocking)
n_gpu = 1
command = f'dlojz.py -b {bs_optim} --image-size {image_size} --test'
command
'dlojz.py -b 512 --image-size 176 --test'
Soumission du job. Attention vous sollicitez les noeuds de calcul à ce moment-là.
Pour soumettre le job, veuillez basculer la cellule suivante du mode Raw NBConvert au mode Code.
Copier-coller la sortie jobids = ['xxxxx', ...] dans la cellule suivante.
Puis, rebasculer la cellule précédente en mode Raw NBConvert, afin d'eviter de relancer un job par erreur.
#jobid = ['1493619']
display_slurm_queue(name)
Done!
controle_technique(jobid)
Train throughput: 1710.33 images/second GPU throughput: 1712.56 images/second epoch time: 749.29 seconds training time estimation for 90 epochs (with validations): 21.61 hours ----------- training step time average (fwd/bkwd on GPU): 0.298968 sec (43.3%/62.0%) +/- 0.051283 loading step time average (CPU to GPU): 0.000389 sec +/- 0.000048 ----------- ELIGIBLE to run 41 epochs
Afin de mesurer l'impact de la taille de batch sur l'occupation mémoire et sur le throughput, la cellule suivante permet de soumettre plusieurs jobs avec des tailles de batch croissantes. Dans les cas où la mémoire est saturée et dépasse la capacité du GPU, le système renverra une erreur CUDA Out of Memory.
Soumission du job. Attention vous sollicitez les noeuds de calcul à ce moment-là.
Pour soumettre le job, veuillez basculer la cellule suivante du mode Raw NBConvert au mode Code.
Copier-coller la sortie jobids = ['xxxxx', ...] dans la cellule suivante.
Puis, rebasculer la cellule précédente en mode Raw NBConvert, afin d'eviter de relancer un job par erreur.
#jobids = ['1493654', '1493656', '1493657', '1493658', '1493659', '1493660', '1493661', '1493662']
display_slurm_queue(name)
Done!
GPU_underthehood(jobids)
Batch size per GPU: 16 Max GPU Memory Allocated: 1.69 GB, Troughput: 380.611 images/second Batch size per GPU: 32 Max GPU Memory Allocated: 1.70 GB, Troughput: 696.125 images/second Batch size per GPU: 64 Max GPU Memory Allocated: 2.02 GB, Troughput: 1132.653 images/second Batch size per GPU: 128 Max GPU Memory Allocated: 3.69 GB, Troughput: 1426.736 images/second Batch size per GPU: 256 Max GPU Memory Allocated: 7.01 GB, Troughput: 1571.190 images/second Batch size per GPU: 512 Max GPU Memory Allocated: 13.67 GB, Troughput: 1711.980 images/second Batch size per GPU: 1024 Max GPU Memory Allocated: 26.96 GB, Troughput: 1594.229 images/second Batch size per GPU: 2048 CUDA out of memory Memory occupancy by Model part : 0.365 +/- 0.015 GB

TODO : dans le script dlojz.py :
import torch.distributed as dist
from torch.nn.parallel import DistributedDataParallel
# configure distribution method: define rank and initialise communication backend (NCCL)
dist.init_process_group(backend='nccl', init_method='env://',
world_size=idr_torch.size, rank=idr_torch.rank)
# define model
torch.cuda.set_device(idr_torch.local_rank)
gpu = torch.device("cuda")
model = DistributedDataParallel(model, device_ids=[idr_torch.local_rank])
train_loader et val_loader.train_sampler = torch.utils.data.distributed.DistributedSampler(train_dataset,
num_replicas=idr_torch.size,
rank=idr_torch.rank,
shuffle=True)
val_sampler = torch.utils.data.distributed.DistributedSampler(val_dataset,
num_replicas=idr_torch.size,
rank=idr_torch.rank,
shuffle=False)
Dans train_loader et val_loader, ajouter la ligne sampler=train_sampler, (ou sampler=val_sampler,) pour associer le bon sampler et basculer le shuffle= du loader à False pour ne pas avoir de conflit avec le shuffle du sampler.
Au tout début de la boucle d'apprentissage indiquer au sampler l'epoch, afin d'obtenir un shuffle différent à chaque epoch.
#### TRAINING ############
for epoch in range(args.epochs):
train_sampler.set_epoch(epoch)
# Metric mesurement
_, predicted = torch.max(outputs.data, 1)
accuracy = (predicted == labels).sum() / labels.size(0)
dist.all_reduce(accuracy, op=dist.ReduceOp.SUM)
accuracy /= idr_torch.size
if idr_torch.rank == 0: accuracies.append(accuracy.item())
for iv, (val_images, val_labels) in enumerate(val_loader):
...
...
dist.all_reduce(val_loss, op=dist.ReduceOp.SUM)
dist.all_reduce(val_accuracy, op=dist.ReduceOp.SUM)
A noter : la moyenne des métriques distribuées sur les différents GPU se calcule pour la validation différemment du training. Ici la métrique est pondérée par rapport à la taille globale du dataset de validation. Il n'est donc pas nécessaire de diviser par idr_torch.size.
## Be sure all process finish at the same time to avoid incoherent logs at the end of process
dist.barrier()
command = f'dlojz.py -b {bs_optim} --image-size {image_size} --test'
command
'dlojz.py -b 512 --image-size 176 --test'
Soumission du job. Attention vous sollicitez les noeuds de calcul à ce moment-là.
Pour soumettre le job, veuillez basculer la cellule suivante du mode Raw NBConvert au mode Code.
Copier-coller la sortie jobid = ['xxxxx'] dans la cellule suivante.
Puis, rebasculer la cellule précédente en mode Raw NBConvert, afin d'eviter de relancer un job par erreur.
#jobid = ['1493733']
display_slurm_queue(name)
Done!
controle_technique(jobid)
Train throughput: 6106.79 images/second GPU throughput: 6129.83 images/second epoch time: 209.94 seconds training time estimation for 90 epochs (with validations): 6.07 hours ----------- training step time average (fwd/bkwd on GPU): 0.334104 sec (44.3%/62.3%) +/- 0.065673 loading step time average (CPU to GPU): 0.001261 sec +/- 0.005544 ----------- ELIGIBLE to run 39 epochs


Le but de ce TP est de paramétrer l'entraînement pour participer à la course Imagenet Racing.
Les job de chaque participant durant environ 30 minutes, s'exécuteront pendant la nuit. Les résultats seront commentés le lendemain.
from dlojz_tools import plot_accuracy, imagenet_starter, plot_time
TODO : Chercher les bons paramètres, notamment la taille de batch par GPU batch_size et la taille d'image image_size permettant d'avoir un bon équilibre (d'après votre intuition) entre une taille d'image suffisante et un nombre d'epochs suffisant.
Le nombre d'epochs auquel vous avez le droit dépend du Throughput mesuré pendant le test. Il faudra regarder la dernière ligne du test Eligible to run X epochs pour connaître cette mesure.
Afin de mesurer l'impact de la taille de batch sur l'occupation mémoire et sur le throughput, la cellule suivante permet de soumettre plusieurs jobs avec des tailles de batch croissantes. Dans les cas où la mémoire est saturée et dépasse la capacité du GPU, le système renverra une erreur CUDA Out of Memory.
Soumission du job. Attention vous sollicitez les noeuds de calcul à ce moment-là.
Pour soumettre le job, veuillez basculer la cellule suivante du mode Raw NBConvert au mode Code.
## TODO : Définir une taille d'image
image_size = 176
#jobids = ['1493910', '1493914', '1493916', '1493918', '1493920', '1493932', '1493937']
display_slurm_queue(name)
Done!
GPU_underthehood(jobids)
Batch size per GPU: 32 Max GPU Memory Allocated: 1.79 GB, Troughput: 638.585 images/second Batch size per GPU: 64 Max GPU Memory Allocated: 2.11 GB, Troughput: 1087.999 images/second Batch size per GPU: 128 Max GPU Memory Allocated: 3.78 GB, Troughput: 1409.180 images/second Batch size per GPU: 256 Max GPU Memory Allocated: 7.11 GB, Troughput: 1572.290 images/second Batch size per GPU: 512 Max GPU Memory Allocated: 13.77 GB, Troughput: 1693.853 images/second Batch size per GPU: 1024 Max GPU Memory Allocated: 27.05 GB, Troughput: 1532.855 images/second Batch size per GPU: 2048 CUDA out of memory Memory occupancy by Model part : 0.460 +/- 0.018 GB
## TODO : Choisir un batch size optimal
batch_size = 512
Soumission du job. Attention vous sollicitez les noeuds de calcul à ce moment-là.
Pour soumettre le job, veuillez basculer la cellule suivante du mode Raw NBConvert au mode Code.
Copier-coller la sortie jobid = ['xxxxx'] dans la cellule suivante.
Nous vous conseillons de garder plusieurs lignes en mémoire afin de pouvoir comparer facilement vos différentes expériences.
#jobid = ['1494033']
display_slurm_queue(name)
Done!
controle_technique(jobid)
Train throughput: 1669.81 images/second GPU throughput: 1777.92 images/second epoch time: 767.47 seconds training time estimation for 90 epochs (with validations): 21.91 hours ----------- training step time average (fwd/bkwd on GPU): 0.287977 sec (39.3%/64.2%) +/- 0.051004 loading step time average (CPU to GPU): 0.018645 sec +/- 0.008375 ----------- ELIGIBLE to run 41 epochs
TODO : Une fois que vous avez choisi la configuration que vous souhaitez engager pour la course, la fonction suivante permet de générer la bonne commande à soumettre à SLURM avec le bon nombre d'epochs, les bonnes configurations de taille de batch par GPU et de taille d'image, à condition d'avoir fourni le bon jobid.
?imagenet_starter
Signature: imagenet_starter( jobid, lr=None, moment=0.9, weight_decay=0.0005, jour2=False, ) Docstring: <no docstring> File: /gpfsdswork/projects/idris/sos/ssos040/DLO_JZ_ref/formation-ia-avancee/dlojz_tools.py Type: function
#jobid = ['1292862']
command = imagenet_starter(jobid, weight_decay=5e-4)
command
'dlojz.py -b 512 -e 41 --image-size 176 --lr 1.0 --mom 0.9 --wd 0.0005'
Soumission du job. Attention vous sollicitez les noeuds de calcul à ce moment-là.
Pour soumettre le job, veuillez basculer la cellule suivante du mode Raw NBConvert au mode Code.
Copier-coller la sortie jobid = ['xxxxx'] dans la cellule suivante.
Puis, rebasculer la cellule précédente en mode Raw NBConvert, afin d'eviter de relancer un job par erreur.
#jobid = ['1494173']
jobids = ['1494173', jobid[0]]
plot_accuracy(jobids[:1])
Resnet-50: SGD bs: 16384 is: 176 lr: 1.0 wd: 0.0005 top-1: 0.7212 >>> Training complete in: 0:30:34.607509
plot_time(jobids[:1])
display_slurm_queue(name)
Done!
plot_accuracy(jobids)
plot_time(jobid)
Décommenter la ligne #!wandb sync --sync-all pour publier les résultats sur le dépôt WandB
import os
os.environ['WANDB_API_KEY']='2ecf1cc3a3fe45c17b480e66dd0f390c85763d42'
#!wandb sync --sync-all
Syncing: https://wandb.ai/dlojz/Imagenet%20Race%20Cup/runs/1n2fk6p4 ...done.